#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include "log.h"

#if defined(LOG_WIN32)
// #pragma warning (disable: 4244) // conversion loss of data
#include <windows.h>
#include <io.h>
#define hmutex_t CRITICAL_SECTION
#define hmutex_init InitializeCriticalSection
#define hmutex_destroy DeleteCriticalSection
#define hmutex_lock EnterCriticalSection
#define hmutex_unlock LeaveCriticalSection
#elif defined(LOG_LINUX)
#include <sys/time.h> // for gettimeofday
#include <pthread.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#define hmutex_t pthread_mutex_t
#define hmutex_init(mutex) pthread_mutex_init(mutex, NULL)
#define hmutex_destroy pthread_mutex_destroy
#define hmutex_lock pthread_mutex_lock
#define hmutex_unlock pthread_mutex_unlock
#else
#if 1
#define hmutex_t void *
#define hmutex_init(mutex) ((void)mutex)
#define hmutex_destroy(mutex) ((void)mutex)
#define hmutex_lock(mutex) ((void)mutex)
#define hmutex_unlock(mutex) ((void)mutex)
#else
#include "rtthread.h"
#define hmutex_t struct rt_mutex
#define hmutex_init(mutex) rt_mutex_init(mutex, NULL, RT_IPC_FLAG_FIFO)
#define hmutex_destroy(mutex)
#define hmutex_lock(mutex) rt_mutex_take(mutex, RT_WAITING_FOREVER)
#define hmutex_unlock(mutex) rt_mutex_release(mutex)
#endif
#endif

#define DEBUG_PRINT(fmt, ...) do { \
    printf(fmt, ##__VA_ARGS__); \
    fflush(stdout); \
} while (0)

// =============================================================================
// private function

// =============================================================================
// types and definitions

typedef struct
{
    log_function_t fn;
    log_level_t threshold;
} subscriber_t;

typedef struct {
    char filename[FILE_NAME_MAX_LEN];
    time_t creation_time;
} FileInfo;

// =============================================================================
// local storage

static subscriber_t s_subscribers[LOG_MAX_SUBSCRIBERS];
static char message[LOG_MAX_MESSAGE_LENGTH];

static log_t log = {0};
static struct tm *gTm = NULL;// 用于获取每次日志产生的时间
// =============================================================================
// thread-safe

static hmutex_t s_mutex;

// =============================================================================
// file count control

// =============================================================================
// user-visible code
static int create_dir(const char *target_dir)
{
    int result = 0;
    struct stat st;
    if (stat(target_dir, &st) != 0)
    {
        DEBUG_PRINT("Dir %s not found, creating...\n", target_dir);
        result = mkdir(target_dir, 0777);
    }
    return result;
}

static int compare(const void *a, const void *b)
{
    const FileInfo *file1 = (const FileInfo *)a;
    const FileInfo *file2 = (const FileInfo *)b;
    return difftime(file1->creation_time, file2->creation_time);
}

static void clear_log_files()
{
    DEBUG_PRINT("Clear logs...\n");

    DIR *dir;
    struct dirent *entry;
    struct stat fileStat;
    int maxFileNum = 20;
    FileInfo files_info[CHECK_FILE_NUM_LIMIT];
    int fileCount = 0;

    dir = opendir(LOG_FILE_DIR_PATH);
    if (dir == NULL) {
        perror("Error opening directory");
        return;
    }

    while ((entry = readdir(dir)) != NULL) {
        if (strncmp(entry->d_name, LOG_FILE_PREFIX, strlen(LOG_FILE_PREFIX)) == 0 &&
            strstr(entry->d_name, ".log") != NULL)
        {
            char path[256];
            sprintf(path, "%s/%s", LOG_FILE_DIR_PATH, entry->d_name);
            if (stat(path, &fileStat) == 0) {
                strncpy(files_info[fileCount].filename, entry->d_name, sizeof(files_info[fileCount].filename));
                files_info[fileCount].creation_time = fileStat.st_ctime;
                fileCount ++;

                if (fileCount >= CHECK_FILE_NUM_LIMIT)
                {
                    DEBUG_PRINT("Exceeded the maximum file limit. Ignoring additional files.\n");
                    break;
                }
            }
        }
    }

    closedir(dir);

    qsort(files_info, fileCount, sizeof(FileInfo), compare);

    // Delete the earliest n files
    int n = fileCount - MAX_FILE_NUM_LIMIT;
    for (int i = 0; i < n && i < fileCount; i++) {
        char path[256];
        sprintf(path, "%s/%s", LOG_FILE_DIR_PATH, files_info[i].filename);
        if (unlink(path) == 0) {
            DEBUG_PRINT("Deleted: %s\n", files_info[i].filename);
        } else {
            perror("Error deleting file");
        }
    }
}

void log_create()
{
    char fname[FILE_NAME_MAX_LEN];
    time_t t = time(NULL);
    struct tm *ts = localtime(&t);
    sprintf(fname, "%s/%s-%04d%02d%02d.log", LOG_FILE_DIR_PATH, LOG_FILE_PREFIX, ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday);
    strncpy(log.filename, fname, sizeof(log.filename) - 1);
    log.filename[sizeof(log.filename) - 1] = '\0'; // 确保以空字符结尾

    log.file = fopen(fname, "a+");
    log.year = ts->tm_year;
    log.month = ts->tm_mon;
    log.day = ts->tm_mday;

    DEBUG_PRINT("Log file created: %s\n", log.filename);
}


void log_destroy()
{
    if (log.file) {
        fclose(log.file);
        log.file = NULL;
    }

    // 清空文件名和重置其他字段
    memset(log.filename, 0, sizeof(log.filename));
    log.size = 0;
    log.year = 0;
    log.month = 0;
    log.day = 0;
}

static char log_name_expired(struct tm *tm)
{
    char ret = 0;

    if (NULL == tm)
    {
        time_t t = time(NULL);
        tm = localtime(&t);
    }

    if(log.day != tm->tm_mday || log.month != tm->tm_mon || log.year != tm->tm_year) ret = 1;

    return ret;
}

static void log_rotate()
{
    if (!log.file)
    {
        DEBUG_PRINT("log.file is null\n");
        return;
    }

    char needRotateLog = 0;

    // check if the log name has expired
    if(log_name_expired(gTm))
    {
        DEBUG_PRINT("log filename has expired\n");
        needRotateLog = 1;
    }

    // check if the log size exceeds the limit
    if (log.size > MAX_LOG_SIZE)
    {
        DEBUG_PRINT("log size exceeds the limit\n");
        needRotateLog = 1;
    }

    // rotate log
    if (needRotateLog)
    {
        time_t now = time(NULL);
        char new_file_name[FILE_NAME_MAX_LEN];
        char origin_file_name[FILE_NAME_MAX_LEN];
        memcpy(origin_file_name, log.filename, sizeof(log.filename));
        snprintf(new_file_name, sizeof(new_file_name), "%s.%ld", origin_file_name, (long)now);

        // close origin log file
        log_destroy();

        // rename origin log file to new file name
        if (rename(origin_file_name, new_file_name) == 0)
        {
            DEBUG_PRINT("success to rotate log\n");
        }
        else
        {
            DEBUG_PRINT("FAIL to rotate log\n");
        }

        // create new log file
        log_create();

        // clear logs
        clear_log_files();
    }
}

void log_init()
{
    if (create_dir(LOG_FILE_DIR_PATH) != 0)
    {
        DEBUG_PRINT("failed to create log dir\n");
        return;
    }
    memset(s_subscribers, 0, sizeof(s_subscribers));
    hmutex_init(&s_mutex);
#if defined(LOG_WIN32) || defined(LOG_LINUX)
    log_create();
    clear_log_files();
    atexit(log_deinit); // 程序退出自动调用log_deinit函数
#endif
}

void log_deinit()
{
    hmutex_destroy(&s_mutex);
    log_destroy();
}

// search the s_subscribers table to install or update fn
log_err_t log_subscribe(log_function_t fn, log_level_t threshold)
{
    int available_slot = -1;
    int i;
    hmutex_lock(&s_mutex);
    for (i = 0; i < LOG_MAX_SUBSCRIBERS; i++)
    {
        if (s_subscribers[i].fn == fn)
        {
            // already subscribed: update threshold and return immediately.
            s_subscribers[i].threshold = threshold;
            hmutex_unlock(&s_mutex);
            return LOG_ERR_NONE;
        }
        else if (s_subscribers[i].fn == NULL)
        {
            // found a free slot
            available_slot = i;
        }
    }
    // fn is not yet a subscriber.  assign if possible.
    if (available_slot == -1)
    {
        hmutex_unlock(&s_mutex);
        return LOG_ERR_SUBSCRIBERS_EXCEEDED;
    }
    s_subscribers[available_slot].fn = fn;
    s_subscribers[available_slot].threshold = threshold;
    hmutex_unlock(&s_mutex);
    return LOG_ERR_NONE;
}

// search the s_subscribers table to remove
log_err_t log_unsubscribe(log_function_t fn)
{
    int i;
    hmutex_lock(&s_mutex);
    for (i = 0; i < LOG_MAX_SUBSCRIBERS; i++)
    {
        if (s_subscribers[i].fn == fn)
        {
            s_subscribers[i].fn = NULL; // mark as empty
            hmutex_unlock(&s_mutex);
            return LOG_ERR_NONE;
        }
    }
    hmutex_unlock(&s_mutex);
    return LOG_ERR_NOT_SUBSCRIBED;
}

const char *log_level_name(log_level_t severity)
{
    switch (severity)
    {
    case LOG_TRACE_LEVEL:
        return "TRACE";
    case LOG_DEBUG_LEVEL:
        return "DEBUG";
    case LOG_INFO_LEVEL:
        return "INFO";
    case LOG_WARN_LEVEL:
        return "WARN";
    case LOG_ERROR_LEVEL:
        return "ERROR";
    case LOG_FATAL_LEVEL:
        return "FATAL";
    default:
        return "UNKNOW";
    }
}

const char *log_level_color(log_level_t severity)
{
    switch (severity)
    {
    case LOG_TRACE_LEVEL:
        return CLR_SKYBLUE;
    case LOG_DEBUG_LEVEL:
        return CLR_WHITE;
    case LOG_INFO_LEVEL:
        return CLR_GREEN;
    case LOG_WARN_LEVEL:
        return CLR_YELLOW;
    case LOG_ERROR_LEVEL:
        return CLR_RED;
    case LOG_FATAL_LEVEL:
        return CLR_RED_WHT;
    default:
        return CLR_CLR;
    }
}

int log_printf(log_level_t severity, const char *fmt, ...)
{
    va_list ap;
    int i = 0, len = 0;

    if (severity < LOG_FATAL_LEVEL || severity > LOG_TRACE_LEVEL)
        return -1;

    hmutex_lock(&s_mutex);

    va_start(ap, fmt);
    len += vsnprintf(message + len, LOG_MAX_MESSAGE_LENGTH - len, fmt, ap);
    va_end(ap);

    for (i = 0; i < LOG_MAX_SUBSCRIBERS; i++)
    {
        if (s_subscribers[i].fn != NULL)
        {
            if (severity <= s_subscribers[i].threshold)
            {
#ifndef LOG_CONSOLE_COLOR_ENABLE
                s_subscribers[i].fn(message, len);
#else
                const char *ps = NULL, *pe = NULL;
                ps = log_level_color(severity);
                pe = CLR_CLR;
                s_subscribers[i].fn(ps, strlen(ps));
                s_subscribers[i].fn(message, len);
                s_subscribers[i].fn(pe, strlen(pe));
#endif
            }
        }
    }

    hmutex_unlock(&s_mutex);

    return len;
}

/*============================================== 自定义实现以下三个函数 ====================================================*/

// #include "rltime.h"

const char *log_timestamp(void)
{
    static char buf[64];
    hmutex_lock(&s_mutex);
#if defined(LOG_WIN32)
    int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, us = 0;
    SYSTEMTIME tm;
    GetLocalTime(&tm);
    year = tm.wYear;
    month = tm.wMonth;
    day = tm.wDay;
    hour = tm.wHour;
    min = tm.wMinute;
    sec = tm.wSecond;
    us = tm.wMilliseconds * 1000;
    sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%03d ", year, month, day, hour, min, sec, us / 1000);
#elif defined(LOG_LINUX)
    int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, us = 0;
    struct timeval tv;
    // struct tm *tm = NULL;
    gettimeofday(&tv, NULL);
    time_t tt = tv.tv_sec;
    gTm = localtime(&tt);
    year = gTm->tm_year + 1900;
    month = gTm->tm_mon + 1;
    day = gTm->tm_mday;
    hour = gTm->tm_hour;
    min = gTm->tm_min;
    sec = gTm->tm_sec;
    us = tv.tv_usec;
    sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%03d ", year, month, day, hour, min, sec, us / 1000);
#else
    buf[0] = 0;
    {
        rl_time_t t = {0};
        uint32_t ts = rl_time(NULL);
        rl_timepack(ts, &t);
        snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d ", t.year, t.mon, t.mday, t.hour, t.min, t.sec);
    }
#endif
    hmutex_unlock(&s_mutex);
    return buf;
}

#if (!defined(LOG_WIN32)) && (!defined(LOG_LINUX))
#include "usart.h"
#endif

void log_console_output(const char *msg, uint32_t len)
{
#if defined(LOG_WIN32) || defined(LOG_LINUX)
    fputs(msg, stdout);
    fflush(stdout);
#else
    USARTx_SendData(USART1, (uint8_t *)msg, len);
#endif
}

#if defined(LOG_WIN32) || defined(LOG_LINUX)

void log_file_output(const char *msg, uint32_t len)
{
    log_rotate();

    if (log.file)
    {
        fwrite(msg, 1, len, log.file);
        log.size = ftell(log.file);
    }
}

#endif

/*
    LOG_INIT();
    LOG_SUBSCRIBE(log_console_output, LOG_DEBUG_LEVEL);
*/
